/* CWebPage.c

This is a Win32 C application (ie, no MFC, WTL, nor even any C++ -- just plain C) that demonstrates
how to embed a browser "control" (actually, an OLE object) in your own window (in order to display a
web page, or an HTML file on disk).

This is very loosely based upon a C++ example written by Chris Becke. I used that to learn the minimum
of what I needed to know about hosting the browser object. Then I wrote this example from the ground up
in C.
*/

#include <windows.h>
#include <exdisp.h>        // Defines of stuff like IWebBrowser2. This is an include file with Visual C 6 and above
#include <mshtml.h>        // Defines of stuff like IHTMLDocument2. This is an include file with Visual C 6 and above
#include <crtdbg.h>        // for _ASSERT()

typedef enum tagOLERENDER
{
    OLERENDER_NONE     = 0,
    OLERENDER_DRAW     = 1,
    OLERENDER_FORMAT   = 2,
    OLERENDER_ASIS     = 3
} OLERENDER;

// A running count of how many windows we have open that contain a browser object
unsigned char WindowCount = 0;

// The class name of our Window to host the browser. It can be anything of your choosing.
static const TCHAR ClassName[] = "Browser Example";

// This is used by DisplayHTMLStr(). It can be global because we never change it.
static const SAFEARRAYBOUND ArrayBound = {1, 0};

// Our IStorage functions that the browser may call
HRESULT STDMETHODCALLTYPE Storage_QueryInterface(IStorage FAR* This, REFIID riid, LPVOID FAR* ppvObj);
HRESULT STDMETHODCALLTYPE Storage_AddRef(IStorage FAR* This);
HRESULT STDMETHODCALLTYPE Storage_Release(IStorage FAR* This);
HRESULT STDMETHODCALLTYPE Storage_CreateStream(IStorage FAR* This, const WCHAR *pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStream **ppstm);
HRESULT STDMETHODCALLTYPE Storage_OpenStream(IStorage FAR* This, const WCHAR * pwcsName, void *reserved1, DWORD grfMode, DWORD reserved2, IStream **ppstm);
HRESULT STDMETHODCALLTYPE Storage_CreateStorage(IStorage FAR* This, const WCHAR *pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStorage **ppstg);
HRESULT STDMETHODCALLTYPE Storage_OpenStorage(IStorage FAR* This, const WCHAR * pwcsName, IStorage * pstgPriority, DWORD grfMode, SNB snbExclude, DWORD reserved, IStorage **ppstg);
HRESULT STDMETHODCALLTYPE Storage_CopyTo(IStorage FAR* This, DWORD ciidExclude, IID const *rgiidExclude, SNB snbExclude,IStorage *pstgDest);
HRESULT STDMETHODCALLTYPE Storage_MoveElementTo(IStorage FAR* This, const OLECHAR *pwcsName,IStorage * pstgDest, const OLECHAR *pwcsNewName, DWORD grfFlags);
HRESULT STDMETHODCALLTYPE Storage_Commit(IStorage FAR* This, DWORD grfCommitFlags);
HRESULT STDMETHODCALLTYPE Storage_Revert(IStorage FAR* This);
HRESULT STDMETHODCALLTYPE Storage_EnumElements(IStorage FAR* This, DWORD reserved1, void * reserved2, DWORD reserved3, IEnumSTATSTG ** ppenum);
HRESULT STDMETHODCALLTYPE Storage_DestroyElement(IStorage FAR* This, const OLECHAR *pwcsName);
HRESULT STDMETHODCALLTYPE Storage_RenameElement(IStorage FAR* This, const WCHAR *pwcsOldName, const WCHAR *pwcsNewName);
HRESULT STDMETHODCALLTYPE Storage_SetElementTimes(IStorage FAR* This, const WCHAR *pwcsName, FILETIME const *pctime, FILETIME const *patime, FILETIME const *pmtime);
HRESULT STDMETHODCALLTYPE Storage_SetClass(IStorage FAR* This, REFCLSID clsid);
HRESULT STDMETHODCALLTYPE Storage_SetStateBits(IStorage FAR* This, DWORD grfStateBits, DWORD grfMask);
HRESULT STDMETHODCALLTYPE Storage_Stat(IStorage FAR* This, STATSTG * pstatstg, DWORD grfStatFlag);

// Our IStorage VTable. This is the array of pointers to the above functions in our C
// program that someone may call in order to store some data to disk. We must define a
// particular set of functions that comprise the IStorage set of functions (see above),
// and then stuff pointers to those functions in their respective 'slots' in this table.
// We want the browser to use this VTable with our IStorage structure (object).
IStorageVtbl MyIStorageTable = {
    Storage_QueryInterface,
    Storage_AddRef,
    Storage_Release,
    Storage_CreateStream,
    Storage_OpenStream,
    Storage_CreateStorage,
    Storage_OpenStorage,
    Storage_CopyTo,
    Storage_MoveElementTo,
    Storage_Commit,
    Storage_Revert,
    Storage_EnumElements,
    Storage_DestroyElement,
    Storage_RenameElement,
    Storage_SetElementTimes,
    Storage_SetClass,
    Storage_SetStateBits,
    Storage_Stat
};

// Our IStorage structure. NOTE: All it contains is a pointer to our IStorageVtbl, so we can easily initialize it
// here instead of doing that programmably.
IStorage MyIStorage = { &MyIStorageTable };

// Our IOleInPlaceFrame functions that the browser may call
HRESULT STDMETHODCALLTYPE Frame_QueryInterface(IOleInPlaceFrame FAR* This, REFIID riid, LPVOID FAR* ppvObj);
HRESULT STDMETHODCALLTYPE Frame_AddRef(IOleInPlaceFrame FAR* This);
HRESULT STDMETHODCALLTYPE Frame_Release(IOleInPlaceFrame FAR* This);
HRESULT STDMETHODCALLTYPE Frame_GetWindow(IOleInPlaceFrame FAR* This, HWND FAR* lphwnd);
HRESULT STDMETHODCALLTYPE Frame_ContextSensitiveHelp(IOleInPlaceFrame FAR* This, BOOL fEnterMode);
HRESULT STDMETHODCALLTYPE Frame_GetBorder(IOleInPlaceFrame FAR* This, LPRECT lprectBorder);
HRESULT STDMETHODCALLTYPE Frame_RequestBorderSpace(IOleInPlaceFrame FAR* This, LPCBORDERWIDTHS pborderwidths);
HRESULT STDMETHODCALLTYPE Frame_SetBorderSpace(IOleInPlaceFrame FAR* This, LPCBORDERWIDTHS pborderwidths);
HRESULT STDMETHODCALLTYPE Frame_SetActiveObject(IOleInPlaceFrame FAR* This, IOleInPlaceActiveObject *pActiveObject, LPCOLESTR pszObjName);
HRESULT STDMETHODCALLTYPE Frame_InsertMenus(IOleInPlaceFrame FAR* This, HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths);
HRESULT STDMETHODCALLTYPE Frame_SetMenu(IOleInPlaceFrame FAR* This, HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject);
HRESULT STDMETHODCALLTYPE Frame_RemoveMenus(IOleInPlaceFrame FAR* This, HMENU hmenuShared);
HRESULT STDMETHODCALLTYPE Frame_SetStatusText(IOleInPlaceFrame FAR* This, LPCOLESTR pszStatusText);
HRESULT STDMETHODCALLTYPE Frame_EnableModeless(IOleInPlaceFrame FAR* This, BOOL fEnable);
HRESULT STDMETHODCALLTYPE Frame_TranslateAccelerator(IOleInPlaceFrame FAR* This, LPMSG lpmsg, WORD wID);

// Our IOleInPlaceFrame VTable. This is the array of pointers to the above functions in our C
// program that the browser may call in order to interact with our frame window that contains
// the browser object. We must define a particular set of functions that comprise the
// IOleInPlaceFrame set of functions (see above), and then stuff pointers to those functions
// in their respective 'slots' in this table. We want the browser to use this VTable with our
// IOleInPlaceFrame structure.
IOleInPlaceFrameVtbl MyIOleInPlaceFrameTable = {
    Frame_QueryInterface,
    Frame_AddRef,
    Frame_Release,
    Frame_GetWindow,
    Frame_ContextSensitiveHelp,
    Frame_GetBorder,
    Frame_RequestBorderSpace,
    Frame_SetBorderSpace,
    Frame_SetActiveObject,
    Frame_InsertMenus,
    Frame_SetMenu,
    Frame_RemoveMenus,
    Frame_SetStatusText,
    Frame_EnableModeless,
    Frame_TranslateAccelerator
};

// We need to pass an IOleInPlaceFrame struct to the browser object. And one of our IOleInPlaceFrame
// functions (Frame_GetWindow) is going to need to access our window handle. So let's create our own
// struct that starts off with an IOleInPlaceFrame struct (and that's important -- the IOleInPlaceFrame
// struct *must* be first), and then has an extra data field where we can store our own window's HWND.
//
// And because we may want to create multiple windows, each hosting its own browser object (to
// display its own web page), then we need to create a IOleInPlaceFrame struct for each window. So,
// we're not going to declare our IOleInPlaceFrame struct globally. We'll allocate it later using
// GlobalAlloc, and then stuff the appropriate HWND in it then, and also stuff a pointer to
// MyIOleInPlaceFrameTable in it. But let's just define it here.
typedef struct _IOleInPlaceFrameEx {
    IOleInPlaceFrame    frame;        // The IOleInPlaceFrame must be first!

    ///////////////////////////////////////////////////
    // Here you add any variables that you need
    // to access in your IOleInPlaceFrame functions.
    // You don't want those functions to access global
    // variables, because then you couldn't use more
    // than one browser object. (ie, You couldn't have
    // multiple windows, each with its own embedded
    // browser object to display a different web page).
    //
    // So here is where I added my extra HWND that my
    // IOleInPlaceFrame function Frame_GetWindow() needs
    // to access.
    ///////////////////////////////////////////////////
    HWND                window;
} IOleInPlaceFrameEx;

// Our IOleClientSite functions that the browser may call
HRESULT STDMETHODCALLTYPE Site_QueryInterface(IOleClientSite FAR* This, REFIID riid, void ** ppvObject);
HRESULT STDMETHODCALLTYPE Site_AddRef(IOleClientSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_Release(IOleClientSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_SaveObject(IOleClientSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_GetMoniker(IOleClientSite FAR* This, DWORD dwAssign, DWORD dwWhichMoniker, IMoniker ** ppmk);
HRESULT STDMETHODCALLTYPE Site_GetContainer(IOleClientSite FAR* This, LPOLECONTAINER FAR* ppContainer);
HRESULT STDMETHODCALLTYPE Site_ShowObject(IOleClientSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_OnShowWindow(IOleClientSite FAR* This, BOOL fShow);
HRESULT STDMETHODCALLTYPE Site_RequestNewObjectLayout(IOleClientSite FAR* This);

// Our IOleClientSite VTable. This is the array of pointers to the above functions in our C
// program that the browser may call in order to interact with our frame window that contains
// the browser object. We must define a particular set of functions that comprise the
// IOleClientSite set of functions (see above), and then stuff pointers to those functions
// in their respective 'slots' in this table. We want the browser to use this VTable with our
// IOleClientSite structure.
IOleClientSiteVtbl MyIOleClientSiteTable = {
    Site_QueryInterface,
    Site_AddRef,
    Site_Release,
    Site_SaveObject,
    Site_GetMoniker,
    Site_GetContainer,
    Site_ShowObject,
    Site_OnShowWindow,
    Site_RequestNewObjectLayout
};

// Our IOleInPlaceSite functions that the browser may call
HRESULT STDMETHODCALLTYPE Site_GetWindow(IOleInPlaceSite FAR* This, HWND FAR* lphwnd);
HRESULT STDMETHODCALLTYPE Site_ContextSensitiveHelp(IOleInPlaceSite FAR* This, BOOL fEnterMode);
HRESULT STDMETHODCALLTYPE Site_CanInPlaceActivate(IOleInPlaceSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_OnInPlaceActivate(IOleInPlaceSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_OnUIActivate(IOleInPlaceSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_GetWindowContext(IOleInPlaceSite FAR* This, LPOLEINPLACEFRAME FAR* lplpFrame,LPOLEINPLACEUIWINDOW FAR* lplpDoc,LPRECT lprcPosRect,LPRECT lprcClipRect,LPOLEINPLACEFRAMEINFO lpFrameInfo);
HRESULT STDMETHODCALLTYPE Site_Scroll(IOleInPlaceSite FAR* This, SIZE scrollExtent);
HRESULT STDMETHODCALLTYPE Site_OnUIDeactivate(IOleInPlaceSite FAR* This, BOOL fUndoable);
HRESULT STDMETHODCALLTYPE Site_OnInPlaceDeactivate(IOleInPlaceSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_DiscardUndoState(IOleInPlaceSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_DeactivateAndUndo(IOleInPlaceSite FAR* This);
HRESULT STDMETHODCALLTYPE Site_OnPosRectChange(IOleInPlaceSite FAR* This, LPCRECT lprcPosRect);

// Our IOleInPlaceSite VTable. This is the array of pointers to the above functions in our C
// program that the browser may call in order to interact with our frame window that contains
// the browser object. We must define a particular set of functions that comprise the
// IOleInPlaceSite set of functions (see above), and then stuff pointers to those functions
// in their respective 'slots' in this table. We want the browser to use this VTable with our
// IOleInPlaceSite structure.
IOleInPlaceSiteVtbl MyIOleInPlaceSiteTable =  {
    Site_QueryInterface,                                    // This gives a compiler warning because we're using
                                                            // the same function as the MyIOleClientSiteTable uses.
                                                            // And the first arg to that Site_QueryInterface() is
                                                            // a pointer to a IOleClientSite. What we really should
                                                            // have here is a separate function with its first arg
                                                            // as a pointer to a IOleInPlaceSite (even though what
                                                            // will really get passed to it is a _IOleClientSiteEx *).
                                                            // But it's easier to use one function for QueryInterface()
                                                            // in order to support "inheritance". ie, Sometimes, the
                                                            // browser may call your IOleClientSite's QueryInterface
                                                            // in order to get a pointer to your IOleInPlaceSite, or
                                                            // vice versa. This is because these two structs will be
                                                            // passed to the browser inside of a single struct. And
                                                            // that makes them "interconnected" as far as
                                                            // QueryInterface is concerned.
    Site_AddRef,                // Ditto as above.
    Site_Release,                // Ditto as above.
    Site_GetWindow,
    Site_ContextSensitiveHelp,
    Site_CanInPlaceActivate,
    Site_OnInPlaceActivate,
    Site_OnUIActivate,
    Site_GetWindowContext,
    Site_Scroll,
    Site_OnUIDeactivate,
    Site_OnInPlaceDeactivate,
    Site_DiscardUndoState,
    Site_DeactivateAndUndo,
    Site_OnPosRectChange
};

// The structure we need to pass to the browser object must contain an IOleClientSite structure. The
// IOleClientSite struct *must* be first. Our IOleClientSite's QueryInterface() may also be asked to
// return a pointer to our IOleInPlaceSite struct. So we'll need to have that object handy. Plus,
// some of our IOleClientSite and IOleInPlaceSite functions will need to have the HWND to our window,
// and also a pointer to our IOleInPlaceFrame struct. So let's create a single struct that has both
// the IOleClientSite and IOleInPlaceSite structs inside it, and also has an extra data field to
// store a pointer to our IOleInPlaceFrame struct. (The HWND is stored in the IOleInPlaceFrame
// struct, so we can get at it there). As long as the IOleClientSite struct is the first thing, it's
// all ok. We'll call this new struct a _IOleClientSiteEx.
//
// And because we may want to create multiple windows, each hosting its own browser object (to
// display its own web page), then we need to create IOleClientSite and IOleInPlaceSite structs for
// each window. (ie, Each window needs its own _IOleClientSiteEx struct). So, we're not going to
// declare this struct globally. We'll allocate it later using GlobalAlloc, and then store the
// appropriate IOleInPlaceFrame struct pointer then.

typedef struct __IOleInPlaceSiteEx {
    IOleInPlaceSite        inplace;        // My IOleInPlaceSite object. Must be first.

    ///////////////////////////////////////////////////
    // Here you add any variables that you need
    // to access in your IOleInPlaceSite functions.
    // You don't want those functions to access global
    // variables, because then you couldn't use more
    // than one browser object. (ie, You couldn't have
    // multiple windows, each with its own embedded
    // browser object to display a different web page.
    //
    // So here is where I added my extra pointer to my
    // IOleInPlaceFrame struct.
    ///////////////////////////////////////////////////
    IOleInPlaceFrameEx    *frame;
} _IOleInPlaceSiteEx;

typedef struct __IOleClientSiteEx {
    IOleClientSite        client;            // My IOleClientSite object. Must be first.
    _IOleInPlaceSiteEx    inplace;        // My IOleInPlaceSite object.

    ///////////////////////////////////////////////////
    // Here you add any variables that you need
    // to access in your IOleClientSite functions.
    ///////////////////////////////////////////////////

} _IOleClientSiteEx;


// This is a simple C example. There are lots more things you can control about the browser object, but
// we don't do it in this example. _Many_ of the functions we provide below for the browser to call, will
// never actually be called by the browser in our example. Why? Because we don't do certain things
// with the browser that would require it to call those functions (even though we need to provide
// at least some stub for all of the functions).
//
// So, for these "dummy functions" that we don't expect the browser to call, we'll just stick in some
// assembly code that causes a debugger breakpoint and tells the browser object that we don't support
// the functionality. That way, if you try to do more things with the browser object, and it starts
// calling these "dummy functions", you'll know which ones you should add more meaningful code to.
#define NOTIMPLEMENTED _ASSERT(0); return(E_NOTIMPL)

////////////////////////////////////// My IStorage functions  /////////////////////////////////////////
// NOTE: The browser object doesn't use the IStorage functions, so most of these are us just returning
// E_NOTIMPL so that anyone who *does* call these functions knows nothing is being done here.

HRESULT STDMETHODCALLTYPE Storage_QueryInterface(IStorage FAR* This, REFIID riid, LPVOID FAR* ppvObj)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_AddRef(IStorage FAR* This)
{
    return(1);
}

HRESULT STDMETHODCALLTYPE Storage_Release(IStorage FAR* This)
{
    return(1);
}

HRESULT STDMETHODCALLTYPE Storage_CreateStream(IStorage FAR* This, const WCHAR *pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStream **ppstm)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_OpenStream(IStorage FAR* This, const WCHAR * pwcsName, void *reserved1, DWORD grfMode, DWORD reserved2, IStream **ppstm)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_CreateStorage(IStorage FAR* This, const WCHAR *pwcsName, DWORD grfMode, DWORD reserved1, DWORD reserved2, IStorage **ppstg)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_OpenStorage(IStorage FAR* This, const WCHAR * pwcsName, IStorage * pstgPriority, DWORD grfMode, SNB snbExclude, DWORD reserved, IStorage **ppstg)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_CopyTo(IStorage FAR* This, DWORD ciidExclude, IID const *rgiidExclude, SNB snbExclude,IStorage *pstgDest)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_MoveElementTo(IStorage FAR* This, const OLECHAR *pwcsName,IStorage * pstgDest, const OLECHAR *pwcsNewName, DWORD grfFlags)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_Commit(IStorage FAR* This, DWORD grfCommitFlags)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_Revert(IStorage FAR* This)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_EnumElements(IStorage FAR* This, DWORD reserved1, void * reserved2, DWORD reserved3, IEnumSTATSTG ** ppenum)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_DestroyElement(IStorage FAR* This, const OLECHAR *pwcsName)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_RenameElement(IStorage FAR* This, const WCHAR *pwcsOldName, const WCHAR *pwcsNewName)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_SetElementTimes(IStorage FAR* This, const WCHAR *pwcsName, FILETIME const *pctime, FILETIME const *patime, FILETIME const *pmtime)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_SetClass(IStorage FAR* This, REFCLSID clsid)
{
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Storage_SetStateBits(IStorage FAR* This, DWORD grfStateBits, DWORD grfMask)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Storage_Stat(IStorage FAR* This, STATSTG * pstatstg, DWORD grfStatFlag)
{
    NOTIMPLEMENTED;
}

///////////////////////////////// My IOleClientSite/IOleInPlaceSite functions  /////////////////////////////////

/************************* Site_QueryInterface() *************************
 * The browser object calls this when it wants a pointer to one of our
 * IOleClientSite or IOleInPlaceSite structures. They are both in the
 * _IOleClientSiteEx struct we allocated in EmbedBrowserObject() and
 * passed to DoVerb() and OleCreate().
 *
 * This =        A pointer to whatever _IOleClientSiteEx struct we passed to
 *                OleCreate() or DoVerb().
 * riid =        A GUID struct that the browser passes us to clue us as to
 *                which type of struct (object) it would like a pointer
 *                returned for.
 * ppvObject =    Where the browser wants us to return a pointer to the
 *                appropriate struct. (ie, It passes us a handle to fill in).
 *
 * RETURNS: S_OK if we return the struct, or E_NOINTERFACE if we don't have
 * the requested struct.
 */

HRESULT STDMETHODCALLTYPE Site_QueryInterface(IOleClientSite FAR* This, REFIID riid, void ** ppvObject)
{
    // It just so happens that the first arg passed to us is our _IOleClientSiteEx struct we allocated
    // and passed to DoVerb() and OleCreate(). Nevermind that 'This' is declared is an IOleClientSite *.
    // Remember that in EmbedBrowserObject(), we allocated our own _IOleClientSiteEx struct, and lied
    // to OleCreate() and DoVerb() -- passing our _IOleClientSiteEx struct and saying it was an
    // IOleClientSite struct. It's ok. An _IOleClientSiteEx starts with an embedded IOleClientSite, so
    // the browser didn't mind. So that's what the browser object is passing us now. The browser doesn't
    // know that it's really an _IOleClientSiteEx struct. But we do. So we can recast it and use it as
    // so here.

    // If the browser is asking us to match IID_IOleClientSite, then it wants us to return a pointer to
    // our IOleClientSite struct. Then the browser will use the VTable in that struct to call our
    // IOleClientSite functions. It will also pass this same pointer to all of our IOleClientSite
    // functions.
    //
    // Actually, we're going to lie to the browser again. We're going to return our own _IOleClientSiteEx
    // struct, and tell the browser that it's a IOleClientSite struct. It's ok. The first thing in our
    // _IOleClientSiteEx is an embedded IOleClientSite, so the browser doesn't mind. We want the browser
    // to continue passing our _IOleClientSiteEx pointer wherever it would normally pass a IOleClientSite
    // pointer.
    //
    // The IUnknown interface uses the same VTable as the first object in our _IOleClientSiteEx
    // struct (which happens to be an IOleClientSite). So if the browser is asking us to match
    // IID_IUnknown, then we'll also return a pointer to our _IOleClientSiteEx.

    if (!memcmp(riid, &IID_IUnknown, sizeof(GUID)) || !memcmp(riid, &IID_IOleClientSite, sizeof(GUID)))
        *ppvObject = &((_IOleClientSiteEx *)This)->client;

    // If the browser is asking us to match IID_IOleInPlaceSite, then it wants us to return a pointer to
    // our IOleInPlaceSite struct. Then the browser will use the VTable in that struct to call our
    // IOleInPlaceSite functions.  It will also pass this same pointer to all of our IOleInPlaceSite
    // functions (except for Site_QueryInterface, Site_AddRef, and Site_Release. Those will always get
    // the pointer to our _IOleClientSiteEx.
    //
    // Actually, we're going to lie to the browser. We're going to return our own IOleInPlaceSiteEx
    // struct, and tell the browser that it's a IOleInPlaceSite struct. It's ok. The first thing in
    // our IOleInPlaceSiteEx is an embedded IOleInPlaceSite, so the browser doesn't mind. We want the
    // browser to continue passing our IOleInPlaceSiteEx pointer wherever it would normally pass a
    // IOleInPlaceSite pointer. My, we're really playing dirty tricks on the browser here. heheh.
    else if (!memcmp(riid, &IID_IOleInPlaceSite, sizeof(GUID)))
        *ppvObject = &((_IOleClientSiteEx *)This)->inplace;

    // For other types of objects the browser wants, just report that we don't have any such objects.
    // NOTE: If you want to add additional functionality to your browser hosting, you may need to
    // provide some more objects here. You'll have to investigate what the browser is asking for
    // (ie, what REFIID it is passing).
    else
    {
        *ppvObject = 0;
        return(E_NOINTERFACE);
    }

    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Site_AddRef(IOleClientSite FAR* This)
{
    return(1);
}

HRESULT STDMETHODCALLTYPE Site_Release(IOleClientSite FAR* This)
{
    return(1);
}

HRESULT STDMETHODCALLTYPE Site_SaveObject(IOleClientSite FAR* This)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Site_GetMoniker(IOleClientSite FAR* This, DWORD dwAssign, DWORD dwWhichMoniker, IMoniker ** ppmk)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Site_GetContainer(IOleClientSite FAR* This, LPOLECONTAINER FAR* ppContainer)
{
    // Tell the browser that we are a simple object and don't support a container
    *ppContainer = 0;

    return(E_NOINTERFACE);
}

HRESULT STDMETHODCALLTYPE Site_ShowObject(IOleClientSite FAR* This)
{
    return(NOERROR);
}

HRESULT STDMETHODCALLTYPE Site_OnShowWindow(IOleClientSite FAR* This, BOOL fShow)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Site_RequestNewObjectLayout(IOleClientSite FAR* This)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Site_GetWindow(IOleInPlaceSite FAR* This, HWND FAR* lphwnd)
{
    // Return the HWND of the window that contains this browser object. We stored that
    // HWND in our _IOleInPlaceSiteEx struct. Nevermind that the function declaration for
    // Site_GetWindow says that 'This' is an IOleInPlaceSite *. Remember that in
    // EmbedBrowserObject(), we allocated our own _IOleInPlaceSiteEx struct which
    // contained an embedded IOleInPlaceSite struct within it. And when the browser
    // called Site_QueryInterface() to get a pointer to our IOleInPlaceSite object, we
    // returned a pointer to our _IOleClientSiteEx. The browser doesn't know this. But
    // we do. That's what we're really being passed, so we can recast it and use it as
    // so here.
    *lphwnd = ((_IOleInPlaceSiteEx FAR*)This)->frame->window;

    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Site_ContextSensitiveHelp(IOleInPlaceSite FAR* This, BOOL fEnterMode)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Site_CanInPlaceActivate(IOleInPlaceSite FAR* This)
{
    // Tell the browser we can in place activate
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Site_OnInPlaceActivate(IOleInPlaceSite FAR* This)
{
    // Tell the browser we did it ok
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Site_OnUIActivate(IOleInPlaceSite FAR* This)
{
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Site_GetWindowContext(IOleInPlaceSite FAR* This, LPOLEINPLACEFRAME FAR* lplpFrame, LPOLEINPLACEUIWINDOW FAR* lplpDoc, LPRECT lprcPosRect, LPRECT lprcClipRect, LPOLEINPLACEFRAMEINFO lpFrameInfo)
{
    // Give the browser the pointer to our IOleInPlaceFrame struct. We stored that pointer
    // in our _IOleInPlaceSiteEx struct. Nevermind that the function declaration for
    // Site_GetWindowContext says that 'This' is an IOleInPlaceSite *. Remember that in
    // EmbedBrowserObject(), we allocated our own _IOleInPlaceSiteEx struct which
    // contained an embedded IOleInPlaceSite struct within it. And when the browser
    // called Site_QueryInterface() to get a pointer to our IOleInPlaceSite object, we
    // returned a pointer to our _IOleClientSiteEx. The browser doesn't know this. But
    // we do. That's what we're really being passed, so we can recast it and use it as
    // so here.
    //
    // Actually, we're giving the browser a pointer to our own IOleInPlaceSiteEx struct,
    // but telling the browser that it's a IOleInPlaceSite struct. No problem. Our
    // IOleInPlaceSiteEx starts with an embedded IOleInPlaceSite, so the browser is
    // cool with it. And we want the browser to pass a pointer to this IOleInPlaceSiteEx
    // wherever it would pass a IOleInPlaceSite struct to our IOleInPlaceSite functions.
    *lplpFrame = (LPOLEINPLACEFRAME)((_IOleInPlaceSiteEx FAR*)This)->frame;

    // We have no OLEINPLACEUIWINDOW
    *lplpDoc = 0;

    // Fill in some other info for the browser
    lpFrameInfo->fMDIApp = FALSE;
    lpFrameInfo->hwndFrame = ((IOleInPlaceFrameEx FAR*)*lplpFrame)->window;
    lpFrameInfo->haccel = 0;
    lpFrameInfo->cAccelEntries = 0;

    // Give the browser the dimensions of where it can draw. We give it our entire window to fill
    GetClientRect(lpFrameInfo->hwndFrame, lprcPosRect);
    GetClientRect(lpFrameInfo->hwndFrame, lprcClipRect);

    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Site_Scroll(IOleInPlaceSite FAR* This, SIZE scrollExtent)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Site_OnUIDeactivate(IOleInPlaceSite FAR* This, BOOL fUndoable)
{
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Site_OnInPlaceDeactivate(IOleInPlaceSite FAR* This)
{
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Site_DiscardUndoState(IOleInPlaceSite FAR* This)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Site_DeactivateAndUndo(IOleInPlaceSite FAR* This)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Site_OnPosRectChange(IOleInPlaceSite FAR* This, LPCRECT lprcPosRect)
{
    return(S_OK);
}

////////////////////////////////////// My IOleInPlaceFrame functions  /////////////////////////////////////////

HRESULT STDMETHODCALLTYPE Frame_QueryInterface(IOleInPlaceFrame FAR* This, REFIID riid, LPVOID FAR* ppvObj)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Frame_AddRef(IOleInPlaceFrame FAR* This)
{
    return(1);
}

HRESULT STDMETHODCALLTYPE Frame_Release(IOleInPlaceFrame FAR* This)
{
    return(1);
}

HRESULT STDMETHODCALLTYPE Frame_GetWindow(IOleInPlaceFrame FAR* This, HWND FAR* lphwnd)
{
    // Give the browser the HWND to our window that contains the browser object. We
    // stored that HWND in our IOleInPlaceFrame struct. Nevermind that the function
    // declaration for Frame_GetWindow says that 'This' is an IOleInPlaceFrame *. Remember
    // that in EmbedBrowserObject(), we allocated our own IOleInPlaceFrameEx struct which
    // contained an embedded IOleInPlaceFrame struct within it. And then we lied when
    // Site_GetWindowContext() returned that IOleInPlaceFrameEx. So that's what the
    // browser is passing us. It doesn't know that. But we do. So we can recast it and
    // use it as so here.
    *lphwnd = ((IOleInPlaceFrameEx FAR*)This)->window;
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Frame_ContextSensitiveHelp(IOleInPlaceFrame FAR* This, BOOL fEnterMode)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Frame_GetBorder(IOleInPlaceFrame FAR* This, LPRECT lprectBorder)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Frame_RequestBorderSpace(IOleInPlaceFrame FAR* This, LPCBORDERWIDTHS pborderwidths)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Frame_SetBorderSpace(IOleInPlaceFrame FAR* This, LPCBORDERWIDTHS pborderwidths)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Frame_SetActiveObject(IOleInPlaceFrame FAR* This, IOleInPlaceActiveObject *pActiveObject, LPCOLESTR pszObjName)
{
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Frame_InsertMenus(IOleInPlaceFrame FAR* This, HMENU hmenuShared, LPOLEMENUGROUPWIDTHS lpMenuWidths)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Frame_SetMenu(IOleInPlaceFrame FAR* This, HMENU hmenuShared, HOLEMENU holemenu, HWND hwndActiveObject)
{
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Frame_RemoveMenus(IOleInPlaceFrame FAR* This, HMENU hmenuShared)
{
    NOTIMPLEMENTED;
}

HRESULT STDMETHODCALLTYPE Frame_SetStatusText(IOleInPlaceFrame FAR* This, LPCOLESTR pszStatusText)
{
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Frame_EnableModeless(IOleInPlaceFrame FAR* This, BOOL fEnable)
{
    return(S_OK);
}

HRESULT STDMETHODCALLTYPE Frame_TranslateAccelerator(IOleInPlaceFrame FAR* This, LPMSG lpmsg, WORD wID)
{
    NOTIMPLEMENTED;
}

/*************************** UnEmbedBrowserObject() ************************
 * Called to detach the browser object from our host window, and free its
 * resources, right before we destroy our window.
 *
 * hwnd =        Handle to the window hosting the browser object.
 *
 * NOTE: The pointer to the browser object must have been stored in the
 * window's USERDATA field. In other words, don't call UnEmbedBrowserObject().
 * with a HWND that wasn't successfully passed to EmbedBrowserObject().
 */

void UnEmbedBrowserObject(HWND hwnd)
{
    IOleObject    **browserHandle;
    IOleObject    *browserObject;

    // Retrieve the browser object's pointer we stored in our window's GWL_USERDATA when
    // we initially attached the browser object to this window.
    if ((browserHandle = (IOleObject **)GetWindowLong(hwnd, GWL_USERDATA)))
    {
        // Unembed the browser object, and release its resources.
        browserObject = *browserHandle;
        browserObject->lpVtbl->Close(browserObject, OLECLOSE_NOSAVE);
        browserObject->lpVtbl->Release(browserObject);

        GlobalFree(browserHandle);

        return;
    }

    // You must have called this for a window that wasn't successfully passed to EmbedBrowserObject().
    // Bad boy!
    _ASSERT(0);
}

/******************************* DisplayHTMLStr() ****************************
 * Takes a string containing some HTML BODY, and displays it in the specified
 * window. For example, perhaps you want to display the HTML text of...
 *
 * <P>This is a picture.<P><IMG src="mypic.jpg">
 *
 * hwnd =        Handle to the window hosting the browser object.
 * string =        Pointer to nul-terminated string containing the HTML BODY.
 *                (NOTE: No <BODY></BODY> tags are required in the string).
 *
 * RETURNS: 0 if success, or non-zero if an error.
 *
 * NOTE: EmbedBrowserObject() must have been successfully called once with the
 * specified window, prior to calling this function. You need call
 * EmbedBrowserObject() once only, and then you can make multiple calls to
 * this function to display numerous pages in the specified window.
 */

long DisplayHTMLStr(HWND hwnd, LPCTSTR string)
{
    IWebBrowser2    *webBrowser2;
    LPDISPATCH        lpDispatch;
    IHTMLDocument2    *htmlDoc2;
    IOleObject        *browserObject;
    SAFEARRAY        *sfArray;
    VARIANT            myURL;
    VARIANT            *pVar;
    BSTR            bstr;

    // Retrieve the browser object's pointer we stored in our window's GWL_USERDATA when
    // we initially attached the browser object to this window.
    browserObject = *((IOleObject **)GetWindowLong(hwnd, GWL_USERDATA));

    // Assume an error.
    bstr = 0;

    // We want to get the base address (ie, a pointer) to the IWebBrowser2 object embedded within the browser
    // object, so we can call some of the functions in the former's table.
    if (!browserObject->lpVtbl->QueryInterface(browserObject, &IID_IWebBrowser2, (void**)&webBrowser2))
    {
        // Ok, now the pointer to our IWebBrowser2 object is in 'webBrowser2', and so its VTable is
        // webBrowser2->lpVtbl.

        // Before we can get_Document(), we actually need to have some HTML page loaded in the browser. So,
        // let's load an empty HTML page. Then, once we have that empty page, we can get_Document() and
        // write() to stuff our HTML string into it.
        VariantInit(&myURL);
        myURL.vt = VT_BSTR;
        myURL.bstrVal = SysAllocString(L"about:blank");

        // Call the Navigate2() function to actually display the page.
        webBrowser2->lpVtbl->Navigate2(webBrowser2, &myURL, 0, 0, 0, 0);

        // Free any resources (including the BSTR).
        VariantClear(&myURL);

        // Call the IWebBrowser2 object's get_Document so we can get its DISPATCH object. I don't know why you
        // don't get the DISPATCH object via the browser object's QueryInterface(), but you don't.
        if (!webBrowser2->lpVtbl->get_Document(webBrowser2, &lpDispatch))
        {
            // We want to get a pointer to the IHTMLDocument2 object embedded within the DISPATCH
            // object, so we can call some of the functions in the former's table.
            if (!lpDispatch->lpVtbl->QueryInterface(lpDispatch, &IID_IHTMLDocument2, (void**)&htmlDoc2))
            {
                // Ok, now the pointer to our IHTMLDocument2 object is in 'htmlDoc2', and so its VTable is
                // htmlDoc2->lpVtbl.

                // Our HTML must be in the form of a BSTR. And it must be passed to write() in an
                // array of "VARIENT" structs. So let's create all that.
                if ((sfArray = SafeArrayCreate(VT_VARIANT, 1, (SAFEARRAYBOUND *)&ArrayBound)))
                {
                    if (!SafeArrayAccessData(sfArray, (void**)&pVar))
                    {
                        pVar->vt = VT_BSTR;
#ifndef UNICODE
                        {
                        wchar_t        *buffer;
                        DWORD        size;

                        size = MultiByteToWideChar(CP_ACP, 0, string, -1, 0, 0);
                        if (!(buffer = (wchar_t *)GlobalAlloc(GMEM_FIXED, sizeof(wchar_t) * size))) goto bad;
                        MultiByteToWideChar(CP_ACP, 0, string, -1, buffer, size);
                        bstr = SysAllocString(buffer);
                        GlobalFree(buffer);
                        }
#else
                        bstr = SysAllocString(string);
#endif
                        // Store our BSTR pointer in the VARIENT.
                        if ((pVar->bstrVal = bstr))
                        {
                            // Pass the VARIENT with its BSTR to write() in order to shove our desired HTML string
                            // into the body of that empty page we created above.
                            htmlDoc2->lpVtbl->write(htmlDoc2, sfArray);

                            // Normally, we'd need to free our BSTR, but SafeArrayDestroy() does it for us
//                            SysFreeString(bstr);
                        }
                    }

                    // Free the array. This also frees the VARIENT that SafeArrayAccessData created for us,
                    // and even frees the BSTR we allocated with SysAllocString
                    SafeArrayDestroy(sfArray);
                }

                // Release the IHTMLDocument2 object.
bad:            htmlDoc2->lpVtbl->Release(htmlDoc2);
            }

            // Release the DISPATCH object.
            lpDispatch->lpVtbl->Release(lpDispatch);
        }

        // Release the IWebBrowser2 object.
        webBrowser2->lpVtbl->Release(webBrowser2);
    }

    // No error?
    if (bstr) return(0);

    // An error
    return(-1);
}

/******************************* DisplayHTMLPage() ****************************
 * Displays a URL, or HTML file on disk.
 *
 * hwnd =        Handle to the window hosting the browser object.
 * webPageName =    Pointer to nul-terminated name of the URL/file.
 *
 * RETURNS: 0 if success, or non-zero if an error.
 *
 * NOTE: EmbedBrowserObject() must have been successfully called once with the
 * specified window, prior to calling this function. You need call
 * EmbedBrowserObject() once only, and then you can make multiple calls to
 * this function to display numerous pages in the specified window.
 */

long DisplayHTMLPage(HWND hwnd, LPTSTR webPageName)
{
    IWebBrowser2    *webBrowser2;
    VARIANT            myURL;
    IOleObject        *browserObject;

    // Retrieve the browser object's pointer we stored in our window's GWL_USERDATA when
    // we initially attached the browser object to this window.
    browserObject = *((IOleObject **)GetWindowLong(hwnd, GWL_USERDATA));

    // We want to get the base address (ie, a pointer) to the IWebBrowser2 object embedded within the browser
    // object, so we can call some of the functions in the former's table.
    if (!browserObject->lpVtbl->QueryInterface(browserObject, &IID_IWebBrowser2, (void**)&webBrowser2))
    {
        // Ok, now the pointer to our IWebBrowser2 object is in 'webBrowser2', and so its VTable is
        // webBrowser2->lpVtbl.

        // Our URL (ie, web address, such as "http://www.microsoft.com" or an HTM filename on disk
        // such as "c:\myfile.htm") must be passed to the IWebBrowser2's Navigate2() function as a BSTR.
        // A BSTR is like a pascal version of a double-byte character string. In other words, the
        // first unsigned short is a count of how many characters are in the string, and then this
        // is followed by those characters, each expressed as an unsigned short (rather than a
        // char). The string is not nul-terminated. The OS function SysAllocString can allocate and
        // copy a UNICODE C string to a BSTR. Of course, we'll need to free that BSTR after we're done
        // with it. If we're not using UNICODE, we first have to convert to a UNICODE string.
        //
        // What's more, our BSTR needs to be stuffed into a VARIENT struct, and that VARIENT struct is
        // then passed to Navigate2(). Why? The VARIENT struct makes it possible to define generic
        // 'datatypes' that can be used with all languages. Not all languages support things like
        // nul-terminated C strings. So, by using a VARIENT, whose first field tells what sort of
        // data (ie, string, float, etc) is in the VARIENT, COM interfaces can be used by just about
        // any language.
        VariantInit(&myURL);
        myURL.vt = VT_BSTR;

#ifndef UNICODE
        {
        wchar_t        *buffer;
        DWORD        size;

        size = MultiByteToWideChar(CP_ACP, 0, webPageName, -1, 0, 0);
        if (!(buffer = (wchar_t *)GlobalAlloc(GMEM_FIXED, sizeof(wchar_t) * size))) goto badalloc;
        MultiByteToWideChar(CP_ACP, 0, webPageName, -1, buffer, size);
        myURL.bstrVal = SysAllocString(buffer);
        GlobalFree(buffer);
        }
#else
        myURL.bstrVal = SysAllocString(webPageName);
#endif
        if (!myURL.bstrVal)
        {
badalloc:    webBrowser2->lpVtbl->Release(webBrowser2);
            return(-6);
        }

        // Call the Navigate2() function to actually display the page.
        webBrowser2->lpVtbl->Navigate2(webBrowser2, &myURL, 0, 0, 0, 0);

        // Free any resources (including the BSTR we allocated above).
        VariantClear(&myURL);

        // We no longer need the IWebBrowser2 object (ie, we don't plan to call any more functions in it,
        // so we can release our hold on it). Note that we'll still maintain our hold on the browser
        // object.
        webBrowser2->lpVtbl->Release(webBrowser2);

        // Success
        return(0);
    }

    return(-5);
}

/***************************** EmbedBrowserObject() **************************
 * Puts the browser object inside our host window, and save a pointer to this
 * window's browser object in the window's GWL_USERDATA field.
 *
 * hwnd =        Handle of our window into which we embed the browser object.
 *
 * RETURNS: 0 if success, or non-zero if an error.
 *
 * NOTE: We tell the browser object to occupy the entire client area of the
 * window.
 *
 * NOTE: No HTML page will be displayed here. We can do that with a subsequent
 * call to either DisplayHTMLPage() or DisplayHTMLStr(). This is merely once-only
 * initialization for using the browser object. In a nutshell, what we do
 * here is get a pointer to the browser object in our window's GWL_USERDATA
 * so we can access that object's functions whenever we want, and we also pass
 * pointers to our IOleClientSite, IOleInPlaceFrame, and IStorage structs so that
 * the browser can call our functions in our struct's VTables.
 */

long EmbedBrowserObject(HWND hwnd)
{
    IOleObject            *browserObject;
    IWebBrowser2        *webBrowser2;
    RECT                rect;
    char                *ptr;
    IOleInPlaceFrameEx    *iOleInPlaceFrameEx;
    _IOleClientSiteEx    *_iOleClientSiteEx;

    // Our IOleClientSite, IOleInPlaceSite, and IOleInPlaceFrame functions need to get our window handle. We
    // could store that in some global. But then, that would mean that our functions would work with only that
    // one window. If we want to create multiple windows, each hosting its own browser object (to display its
    // own web page), then we need to create a unique IOleClientSite, IOleInPlaceSite, and IOleInPlaceFrame
    // structs for each window. (Actually, the IOleClientSite and IOleInPlaceSite must be allocated as a
    // single struct, with the IOleClientSite embedded first). And we'll put an extra field at the end of those
    // structs to store our extra data such as a window handle. Remember that a pointer to our IOleClientSite we
    // create here will be passed as the first arg to every one of our IOleClientSite functions. Ditto with the
    // IOleInPlaceFrame object we create here, and the IOleInPlaceFrame functions. So, our functions are able to
    // retrieve the window handle we'll store here, and then, they'll work with all such windows containing a
    // browser control. Of course, that means we need to GlobalAlloc the structs now. We'll just get all 3 with
    // a single call to GlobalAlloc, but you could do them separately if desired.
    //
    // Um, we're not actually allocating a IOleClientSite, IOleInPlaceSite, and IOleInPlaceFrame structs. Because
    // we're appending our own fields to them, we're getting an IOleInPlaceFrameEx and an _IOleClientSiteEx (which
    // contains both the IOleClientSite and IOleInPlaceSite. But as far as the browser is concerned, it thinks that
    // we're giving it the plain old IOleClientSite, IOleInPlaceSite, and IOleInPlaceFrame.
    //
    // One final thing. We're going to allocate extra room to store the pointer to the browser object.
    if (!(ptr = (char *)GlobalAlloc(GMEM_FIXED, sizeof(IOleInPlaceFrameEx) + sizeof(_IOleClientSiteEx) + sizeof(IOleObject *))))
        return(-1);

    // Initialize our IOleInPlaceFrame object with a pointer to our IOleInPlaceFrame VTable.
    iOleInPlaceFrameEx = (IOleInPlaceFrameEx *)(ptr + sizeof(IOleObject *));
    iOleInPlaceFrameEx->frame.lpVtbl = &MyIOleInPlaceFrameTable;

    // Save our HWND (in the IOleInPlaceFrame object) so our IOleInPlaceFrame functions can retrieve it.
    iOleInPlaceFrameEx->window = hwnd;

    // Initialize our IOleClientSite object with a pointer to our IOleClientSite VTable.
    _iOleClientSiteEx = (_IOleClientSiteEx *)(ptr + sizeof(IOleInPlaceFrameEx) + sizeof(IOleObject *));
    _iOleClientSiteEx->client.lpVtbl = &MyIOleClientSiteTable;

    // Initialize our IOleInPlaceSite object with a pointer to our IOleInPlaceSite VTable.
    _iOleClientSiteEx->inplace.inplace.lpVtbl = &MyIOleInPlaceSiteTable;

    // Save a pointer to our IOleInPlaceFrameEx object so that our IOleInPlaceSite functions can retrieve it.
    _iOleClientSiteEx->inplace.frame = iOleInPlaceFrameEx;

    // Get a pointer to the browser object and lock it down (so it doesn't "disappear" while we're using
    // it in this program). We do this by calling the OS function OleCreate().
    //
    // NOTE: We need this pointer to interact with and control the browser. With normal WIN32 controls such as a
    // Static, Edit, Combobox, etc, you obtain an HWND and send messages to it with SendMessage(). Not so with
    // the browser object. You need to get a pointer to its "base structure" (as returned by OleCreate()). This
    // structure contains an array of pointers to functions you can call within the browser object. Actually, the
    // base structure contains a 'lpVtbl' field that is a pointer to that array. We'll call the array a 'VTable'.
    //
    // For example, the browser object happens to have a SetHostNames() function we want to call. So, after we
    // retrieve the pointer to the browser object (in a local we'll name 'browserObject'), then we can call that
    // function, and pass it args, as so:
    //
    // browserObject->lpVtbl->SetHostNames(browserObject, SomeString, SomeString);
    //
    // There's our pointer to the browser object in 'browserObject'. And there's the pointer to the browser object's
    // VTable in 'browserObject->lpVtbl'. And the pointer to the SetHostNames function happens to be stored in an
    // field named 'SetHostNames' within the VTable. So we are actually indirectly calling SetHostNames by using
    // a pointer to it. That's how you use a VTable.
    //
    // NOTE: We pass our _IOleClientSiteEx struct and lie -- saying that it's a IOleClientSite. It's ok. A
    // _IOleClientSiteEx struct starts with an embedded IOleClientSite. So the browser won't care, and we want
    // this extended struct passed to our IOleClientSite functions.

    if (!OleCreate(&CLSID_WebBrowser, &IID_IOleObject, OLERENDER_DRAW, 0, (IOleClientSite *)_iOleClientSiteEx, &MyIStorage, (void**)&browserObject))
    {
        // Ok, we now have the pointer to the browser object in 'browserObject'. Let's save this in the
        // memory block we allocated above, and then save the pointer to that whole thing in our window's
        // USERDATA field. That way, if we need multiple windows each hosting its own browser object, we can
        // call EmbedBrowserObject() for each one, and easily associate the appropriate browser object with
        // its matching window and its own objects containing per-window data.
        *((IOleObject **)ptr) = browserObject;
        SetWindowLong(hwnd, GWL_USERDATA, (LONG)ptr);

        // We can now call the browser object's SetHostNames function. SetHostNames lets the browser object know our
        // application's name and the name of the document in which we're embedding the browser. (Since we have no
        // document name, we'll pass a 0 for the latter). When the browser object is opened for editing, it displays
        // these names in its titlebar.
        //
        // We are passing 3 args to SetHostNames. You'll note that the first arg to SetHostNames is the base
        // address of our browser control. This is something that you always have to remember when working in C
        // (as opposed to C++). When calling a VTable function, the first arg to that function must always be the
        // structure which contains the VTable. (In this case, that's the browser control itself). Why? That's
        // because that function is always assumed to be written in C++. And the first argument to any C++ function
        // must be its 'this' pointer (ie, the base address of its class, which in this case is our browser object
        // pointer). In C++, you don't have to pass this first arg, because the C++ compiler is smart enough to
        // produce an executable that always adds this first arg. In fact, the C++ compiler is smart enough to
        // know to fetch the function pointer from the VTable, so you don't even need to reference that. In other
        // words, the C++ equivalent code would be:
        //
        // browserObject->SetHostNames(L"My Host Name", 0);
        //
        // So, when you're trying to convert C++ code to C, always remember to add this first arg whenever you're
        // dealing with a VTable (ie, the field is usually named 'lpVtbl') in the standard objects, and also add
        // the reference to the VTable itself.
        //
        // Oh yeah, the L is because we need UNICODE strings. And BTW, the host and document names can be anything
        // you want.

        browserObject->lpVtbl->SetHostNames(browserObject, L"My Host Name", 0);

        GetClientRect(hwnd, &rect);

        // Let browser object know that it is embedded in an OLE container.
        if (!OleSetContainedObject((struct IUnknown *)browserObject, TRUE) &&

            // Set the display area of our browser control the same as our window's size
            // and actually put the browser object into our window.
            !browserObject->lpVtbl->DoVerb(browserObject, OLEIVERB_SHOW, NULL, (IOleClientSite *)_iOleClientSiteEx, -1, hwnd, &rect) &&

            // Ok, now things may seem to get even trickier, One of those function pointers in the browser's VTable is
            // to the QueryInterface() function. What does this function do? It lets us grab the base address of any
            // other object that may be embedded within the browser object. And this other object has its own VTable
            // containing pointers to more functions we can call for that object.
            //
            // We want to get the base address (ie, a pointer) to the IWebBrowser2 object embedded within the browser
            // object, so we can call some of the functions in the former's table. For example, one IWebBrowser2 function
            // we intend to call below will be Navigate2(). So we call the browser object's QueryInterface to get our
            // pointer to the IWebBrowser2 object.
            !browserObject->lpVtbl->QueryInterface(browserObject, &IID_IWebBrowser2, (void**)&webBrowser2))
        {
            // Ok, now the pointer to our IWebBrowser2 object is in 'webBrowser2', and so its VTable is
            // webBrowser2->lpVtbl.

            // Let's call several functions in the IWebBrowser2 object to position the browser display area
            // in our window. The functions we call are put_Left(), put_Top(), put_Width(), and put_Height().
            // Note that we reference the IWebBrowser2 object's VTable to get pointers to those functions. And
            // also note that the first arg we pass to each is the pointer to the IWebBrowser2 object.
            webBrowser2->lpVtbl->put_Left(webBrowser2, 0);
            webBrowser2->lpVtbl->put_Top(webBrowser2, 0);
            webBrowser2->lpVtbl->put_Width(webBrowser2, rect.right);
            webBrowser2->lpVtbl->put_Height(webBrowser2, rect.bottom);

            // We no longer need the IWebBrowser2 object (ie, we don't plan to call any more functions in it
            // right now, so we can release our hold on it). Note that we'll still maintain our hold on the
            // browser object until we're done with that object.
            webBrowser2->lpVtbl->Release(webBrowser2);

            // Success
            return(0);
        }

        // Something went wrong!
        UnEmbedBrowserObject(hwnd);
        return(-3);
    }

    GlobalFree(ptr);
    return(-2);
}

/****************************** WindowProc() ***************************
 * Our message handler for our window to host the browser.
 */

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    if (uMsg == WM_CREATE)
    {
        // Embed the browser object into our host window. We need do this only
        // once. Note that the browser object will start calling some of our
        // IOleInPlaceFrame and IOleClientSite functions as soon as we start
        // calling browser object functions in EmbedBrowserObject().
        if (EmbedBrowserObject(hwnd)) return(-1);

        // Another window created with an embedded browser object
        ++WindowCount;

        // Success
        return(0);
    }

    if (uMsg == WM_DESTROY)
    {
        // Detach the browser object from this window, and free resources.
        UnEmbedBrowserObject(hwnd);

        // One less window
        --WindowCount;

        // If all the windows are now closed, quit this app
        if (!WindowCount) PostQuitMessage(0);

        return(TRUE);
    }

    // NOTE: If you want to resize the area that the browser object occupies when you
    // resize the window, then handle WM_SIZE and use the IWebBrowser2's put_Width()
    // and put_Height() to give it the new dimensions.

    return(DefWindowProc(hwnd, uMsg, wParam, lParam));
}

/****************************** WinMain() ***************************
 * C program entry point.
 *
 * This creates a window to host the web browser, and displays a web
 * page.
 */

int CALLBACK WinMain(HINSTANCE hInstance, HINSTANCE hInstNULL, LPSTR lpszCmdLine, int nCmdShow)
{
    MSG            msg;

    // Initialize the OLE interface. We do this once-only.
    if (OleInitialize(NULL) == S_OK)
    {
        WNDCLASSEX        wc;

        // Register the class of our window to host the browser. 'WindowProc' is our message handler
        // and 'ClassName' is the class name. You can choose any class name you want.
        ZeroMemory(&wc, sizeof(WNDCLASSEX));
        wc.cbSize = sizeof(WNDCLASSEX);
        wc.hInstance = hInstance;
        wc.lpfnWndProc = WindowProc;
        wc.lpszClassName = &ClassName[0];
        RegisterClassEx(&wc);

        // Create a window. NOTE: We embed the browser object duing our WM_CREATE handling for
        // this window.
        if ((msg.hwnd = CreateWindowEx(0, &ClassName[0], "An HTML string", WS_OVERLAPPEDWINDOW,
                            CW_USEDEFAULT, 0, CW_USEDEFAULT, 0,
                            HWND_DESKTOP, NULL, hInstance, 0)))
        {
            // For this window, display a string in the BODY of a web page.
            DisplayHTMLStr(msg.hwnd, "<H2><CENTER>HTML string test</CENTER></H2><P><FONT COLOR=RED>This is a <U>HTML string</U> in memory.</FONT>");

            // Show the window.
            ShowWindow(msg.hwnd, nCmdShow);
            UpdateWindow(msg.hwnd);
        }

        // Create another window with another browser object embedded in it.
        if ((msg.hwnd = CreateWindowEx( 0,
                                        &ClassName[0],
                                        "Microsoft's web site",
                                        WS_OVERLAPPEDWINDOW,
                                        CW_USEDEFAULT,
                                        0,
                                        CW_USEDEFAULT,
                                        0,
                                        HWND_DESKTOP,
                                        NULL,
                                        hInstance,
                                        0 )))
        {
            // For this window, display a URL. This could also be a HTML file on disk such as "c:\\myfile.htm".
            DisplayHTMLPage(msg.hwnd, "http://www.baidu.com");

            // Show the window.
            ShowWindow(msg.hwnd, nCmdShow);
            UpdateWindow(msg.hwnd);
        }

        // Do a message loop until WM_QUIT.
        while (GetMessage(&msg, 0, 0, 0))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }

        // Free the OLE library.
        OleUninitialize();

        return(0);
    }

    MessageBox(0, "Can't open OLE!", "ERROR", MB_OK);
    return(-1);
}
